﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MyMediaLite;
using MyMediaLite.RatingPrediction;
using MyMediaLite.IO;
using MyMediaLite.Data;
using MyMediaLite.Eval;
using System.Globalization;


namespace recommenderSystem
{
    public partial class Form1 : Form
    {
        String basePath;
        String testPath;
        int k;
        MyMediaLite.Data.Ratings baseRating;
        MyMediaLite.Data.Ratings userRating;
        MyMediaLite.Data.Ratings testRating;

        MyParallelHybrid myParallelHybrid;
        MyCascadeHybrid myCascadehybrid;

        //if true slopefirst, else knnfirst
        bool cascadeFlage = false;

        public Form1()
        {
            InitializeComponent();
            baseRating = new MyMediaLite.Data.Ratings();
            userRating = (MyMediaLite.Data.Ratings)RatingData.Read("C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\ml-100k\\ml-100k\\u.data");
            testRating = new MyMediaLite.Data.Ratings();

            //default k
            k = 80;
        }

        //load training set
        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog openDataSet = new OpenFileDialog();

            if (openDataSet.ShowDialog() == DialogResult.OK)
            {
                basePath = openDataSet.FileName;

                baseRating = (MyMediaLite.Data.Ratings)RatingData.Read(basePath);
                textBox1.Text = System.IO.Path.GetFileName(basePath);
                Console.WriteLine("Loaded '{0}'", textBox1.Text);
            }
        }


        private void button2_Click(object sender, EventArgs e)
        {
            /* MyMediaLite.Data.Ratings userRating = (MyMediaLite.Data.Ratings)RatingData.Read("C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\ml-100k\\ml-100k\\u.data");

             IList<int> items = userRating.AllItems;*/

            System.IO.StreamReader myFile =
            new System.IO.StreamReader("C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\ml-100k\\ml-100k\\u.data");
            string myString = myFile.ReadToEnd();

            myFile.Close();


            String[] splitted = myString.Split('\n');

            Array.Sort(splitted);
            System.IO.StreamWriter myWriter =
            new System.IO.StreamWriter("C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\ml-100k\\ml-100k\\uSorted.Data");
            int j = 0;
            for (int i = splitted.Length; i > 0; i--)
            {

                myWriter.WriteLine(splitted[j]);
                j++;
            }
            myWriter.Close();
        }

        //load testset
        private void button3_Click(object sender, EventArgs e)
        {
            OpenFileDialog openDataSet = new OpenFileDialog();

            if (openDataSet.ShowDialog() == DialogResult.OK)
            {
                testPath = openDataSet.FileName;

                testRating = (MyMediaLite.Data.Ratings)RatingData.Read(testPath);
                textBox2.Text = System.IO.Path.GetFileName(testPath);
                Console.WriteLine("Loaded '{0}'", textBox2.Text);
            }
        }


        //Evaluate Slope One
        private void button6_Click(object sender, EventArgs e)
        {
            SlopeOne slopy = new SlopeOne();
            slopy.Ratings = baseRating;

            slopy.Train();

            var result1 = slopy.Evaluate(testRating);

            Console.WriteLine("slope one results: " + result1);

            String output = result1.ToString();

            String[] outArray = output.Split(' ');

            outRMSE.Text = outArray[1];
            outMAE.Text = outArray[3];
            outNMAE.Text = outArray[5];

            //writing result into a File
            slopy.WritePredictions(testRating, "C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\predicted.data");
        }

        private void button5_Click(object sender, EventArgs e)
        {
            UserKNNCosine knn = new UserKNNCosine();

            knn.Ratings = baseRating;
            knn.Train();

            knn.K = (uint)k;

            var results = knn.Evaluate(testRating);

            Console.WriteLine("KNN Results: " + results.ToString());

            String output = results.ToString();

            String[] outArray = output.Split(' ');

            outRMSE.Text = outArray[1];
            outMAE.Text = outArray[3];
            outNMAE.Text = outArray[5];
            knn.WritePredictions(testRating, "C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\predictedKNN.data");
        }

        private void textBox3_TextChanged(object sender, EventArgs e)
        {
            if (textBox3.Text != "")
            {
                k = int.Parse(textBox3.Text);
            }
        }


//===========================================================================================================================
//parallel Hybrid============================================================================================================

        //Calc parallel Hybrid
        private void hybridButton_Click(object sender, EventArgs e)
        {
            // hybrid recommendation
            //if (myParallelHybrid == null)
            //{
                myParallelHybrid = new MyParallelHybrid();
                myParallelHybrid.Ratings = baseRating;
                myParallelHybrid.K = (uint)k;
                myParallelHybrid.Train();

            //}

            // getting threshold
            double threshold = GetThresholdFromTextBox();
            Console.WriteLine("threshold: " + threshold);

            var resRatings = myParallelHybrid.WriteResultWithThreshold(threshold, testRating, "C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\myParallelHybrid.data");
            //myParallelHybrid.WritePredictions(testRating, "C:\\Users\\isolin\\Downloads\\Uni\\2011WS\\Recommender System\\myParallelHybrid.data");

            //evaluate the predicted rating
            var resultParallel = myParallelHybrid.Evaluate(testRating);

            String output = resultParallel.ToString();

            String[] outArray = output.Split(' ');

            outRMSE.Text = outArray[1];
            outMAE.Text = outArray[3];
            outNMAE.Text = outArray[5];

            int[] itemEvaloutput2 = calcItemoutput2(resRatings, threshold, testRating);
            //int[] itemEvaloutput = calcItemoutput(resRatings, threshold, testRating);

            //Console.WriteLine("calc1 parallel");
            //printResultOutput(itemEvaloutput);
            Console.WriteLine("calc2 parallel");
            printResultOutput(itemEvaloutput2);

            //resultListBox.Items.Clear();
            Console.WriteLine("END------------------resultBox");
            Console.WriteLine("ParallelHybrid -> Done!!");
        }

//===============================================================================================================================
//END parallel Hybrid============================================================================================================

//===============================================================================================================================
//CascadeHybrid==================================================================================================================
        //start cascadeHybrid Knn -> slope
        private void hybridKNNSlopy_Click(object sender, EventArgs e)
        {
            Console.WriteLine("=== Hybrid KNN --> Slopy ===");
            cascadeFlage = false;
            handleCascadeHybrid(cascadeFlage);
        }

        //start cascadehybrid slop -> knn
        private void hybridSlopyKNN_Click(object sender, EventArgs e)
        {
            Console.WriteLine("=== Hybrid Slopy --> KNN ===");
            cascadeFlage = true;
            handleCascadeHybrid(cascadeFlage);
        }


        //handle of differend cascadehybrid
        private void handleCascadeHybrid(bool flag)
        {
           // if (myCascadehybrid == null)
            //{
                myCascadehybrid = new MyCascadeHybrid(flag)
                {
                    Ratings = baseRating,
                    K = (uint)k,
                };
                Tools.Tic("mycascade.Train()");
                myCascadehybrid.Train();
                Tools.Toc();
            //}
        
            myCascadehybrid.RecommenderSequence = flag;

            // getting threshold
            double threshold = GetThresholdFromTextBox();

            var resRatings = myCascadehybrid.doItemPrediction(threshold, testRating);

            int[] itemEvaloutput2 = calcItemoutput2(resRatings, threshold, testRating);
            
            Console.WriteLine("calc2 cascade");
            printResultOutput(itemEvaloutput2);

            //resultListBox.Items.Clear();
            Console.WriteLine("cascadeHybrid -> Done!!");
            myCascadehybrid.RecommenderSequence = false;

        }

//====================================================================================================================
//End of Cascade Hybrid===============================================================================================

        //==============================================================================================================================
        //Evaluation Methode for Item Prediction

        //calc the recomment Items: TP, FP, FN
        private int[] calcItemoutput(MyMediaLite.Data.Ratings resRatings, double threshold, MyMediaLite.Data.Ratings testrating)
        {
            Tools.Tic(string.Format("calc1 (predicted = {0} / original = {1}", resRatings.Count, testrating.Count));
            int tp = 0;
            int fp = 0;
            int fn = 0;
            int tn = 0;

            int[] output = new int[4];
            int userId;
            int itemId;
            double trueValue;
            List<int> negativeList = new List<int>();
            int testCount = testrating.Count;

            //index contains in this list should be cut-off
            List<int> cutOffList = new List<int>();

            //calc the positives aka tp & fp, and get ratings list of negatives
            //check all ratings in the resRatings -> all ratings in resRatings are positives
            //all positives will be removed from the cutoffRating list
            for (int index = 0; index < resRatings.Count; index++)
            {
                userId = resRatings.Users[index];
                itemId = resRatings.Items[index];

                //get the real rating based on the positiv list from the ratingList which will be cutoff later
                trueValue = testRating[userId, itemId];

                //if the turevalue is rly greater or even to the threshold its a true positive
                if (trueValue >= threshold)
                {
                    tp++;
                }
                //if truevalue is smaller but in the result then its a false positive
                else
                {
                    fp++;
                }
                var originalIndex = testrating.GetIndex(userId, itemId);
                cutOffList.Add(originalIndex);
            }
            
            //calc for false negatives
            //iterate over wohle testRating, all values wich is not in the cutOffList will be checked

            for (int index = 0; index < testRating.Count; index++)
            {
                userId = testRating.Users[index];
                itemId = testRating.Items[index];


                if (!cutOffList.Contains(index))
                {
                    //all value wich is not in the cut-off list is greater then threshold -> false negatives
                    if (testRating[userId, itemId] >= threshold)
                    {
                        fn++;
                    }
                    else {
                        tn++;
                    }
                }
            }
            output[0] = tp;
            output[1] = fp;
            output[2] = fn;
            output[3] = tn;
            Console.WriteLine("END------------------UTIL");
            //if its  predicted ratings after threshold cut-off is lower and even lower then threshold its a false positive
            Tools.Toc();
            return output;
        }

        //calc the recomment Items: TP, FP, FN
        private int[] calcItemoutput2(MyMediaLite.Data.Ratings predictedRatings, double threshold, MyMediaLite.Data.Ratings originalRatings)
        {
            Tools.Tic(string.Format("calc2 (predicted = {0} / original = {1}", predictedRatings.Count, originalRatings.Count));
            int tp = 0;
            int fp = 0;
            int fn = 0;
            int tn = 0;

            int[] output = new int[4];
            int userId, itemId;
            double trueValue, predictedValue;
            threshold = (int)threshold;

            // loop through all original ratings and see if item is still in predicted list or not so we can calculate tp, fp, fn, tn
            for (int index = 0, count = originalRatings.Count; index < originalRatings.Count; index++)
            {
                userId = originalRatings.Users[index];
                itemId = originalRatings.Items[index];

                if (predictedRatings.TryGet(userId, itemId, out predictedValue))
                {
                    // positives (item is in result)
                    trueValue = originalRatings[userId, itemId];
                    if (trueValue >= threshold)
                    {
                        tp++;
                    }
                    else
                    {
                        fp++;
                    }
                }
                else
                {
                    // negatives (item is NOT in result)
                    trueValue = originalRatings[userId, itemId];
                    if (trueValue >= threshold)
                    {
                        fn++;
                    }
                    else
                    {
                        tn++;
                    }
                }
            }

            output[0] = tp;
            output[1] = fp;
            output[2] = fn;
            output[3] = tn;
            //if its  predicted ratings after threshold cut-off is lower and even lower then threshold its a false positive

            Tools.Toc();
            return output;
        }

        private double GetThresholdFromTextBox()
        {
            double threshold;
            Double.TryParse(thresholdTextBox.Text.Trim().Replace(",", "."), NumberStyles.Any, new CultureInfo("en-US"), out threshold);
            Console.WriteLine("threshold: " + threshold);
            return threshold;
        }

        private void printResultOutput(int[] itemEvaloutput)
        {
            int tp = itemEvaloutput[0];
            int fp = itemEvaloutput[1];
            int fn = itemEvaloutput[2];
            int tn = itemEvaloutput[3];

            Console.WriteLine("tp: " + tp);
            Console.WriteLine("fp: " + fp);
            Console.WriteLine("fn: " + fn);
            Console.WriteLine("tn: " + tn);

            tpcount.Text = tp.ToString();
            fpcount.Text = fp.ToString();
            fncount.Text = fn.ToString();

            ntp.Text = string.Format("{0:0.000}", ((double)tp / (double)(tp + fn)));
            nfp.Text = string.Format("{0:0.000}", ((double)fp / (double)(fp + tn)));
            nfn.Text = string.Format("{0:0.000}", ((double)fn / (double)(tp + fn)));
        }
    }
}

